//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Xml;
using UnityEngine;
using Flare.Display;
using Flare.Geom;

namespace Flare.Text
{
    /// <summary>
    /// The Font class manages device and embedded fonts. It maintains a catalog of font
    /// mappings which allow fonts in a SWF file to be mapped to Unity fonts for improved
    /// rendering.
    /// </summary>
    public class Font
    {
        /// <summary>
        /// The name of the font.
        /// </summary>
        public string fontName { get; 
            /* \cond */ internal set; /* \endcond */ }

        /// <summary>
        /// The style of the font.
        /// </summary>
        public string fontStyle { get; 
            /* \cond */ internal set; /* \endcond */ }

        /// <summary>
        /// The full name of the font which combines its name with its style.
        /// </summary>
        public string fullFontName { get; 
            /* \cond */ private set; /* \endcond */ }

        private static bool ms_fontCatalogsLoaded = false;

        private static Dictionary<string, FontMapping> ms_fontMappings =
            new Dictionary<string, FontMapping>();

        internal Font(string fontName, string fontStyle)
        {
            this.fontName = fontName;
            this.fontStyle = fontStyle;
            this.fullFontName = CombineNameAndStyle(fontName, fontStyle);
        }

        /// <summary>
        /// Get the device font with the specified name and style. If useFallback is
        /// true and the device font isn't located then a fallback font is utilized.
        /// </summary>
        public static DeviceFont GetDeviceFont(string name,
            string style = Flare.Text.FontStyle.REGULAR, bool useFallback = false)
        {
            if (!ms_fontCatalogsLoaded)
            {
                ReadFontCatalogs();
            }

            string key = CombineNameAndStyle(name, style);
            if (ms_fontMappings.ContainsKey(key))
            {
                return ms_fontMappings[key].deviceFont;
            }

            if (useFallback)
            {
                Object[] unityFonts = Resources.LoadAll("Fonts", typeof(UnityEngine.Font));

                // Couldn't find the requested font, so try a fallback
                foreach (var obj in unityFonts)
                {
                    UnityEngine.Font font = obj as UnityEngine.Font;
                    if (font.name.Equals("LiberationSans-Regular"))
                    {
                        Flare.Text.DeviceFont df = new Flare.Text.DeviceFont(name, style, font);
                        return df;
                    }
                }
            }

            return null;
        }

        protected static string CombineNameAndStyle(string name, string style)
        {
            switch (style)
            {
                case Flare.Text.FontStyle.REGULAR:
                    return name;
                    
                case Flare.Text.FontStyle.BOLD_ITALIC:
                    return (name.EndsWith(" Bold Italic")) ? name : (name + " Bold Italic");
                    
                case Flare.Text.FontStyle.BOLD:
                    return (name.EndsWith(" Bold")) ? name : (name + " Bold");
                    
                case Flare.Text.FontStyle.ITALIC:
                    return (name.EndsWith(" Italic")) ? name : (name + " Italic");
                    
                default:
                    return name;
            }
        }

        private static void ReadFontCatalogs()
        {
            // Load all of the Font Catalogs contained within the Resources/Fonts folders
            Object[] fontCatalogs = Resources.LoadAll("Fonts/FontCatalog",
                typeof(UnityEngine.TextAsset));
            
            List<FontMapping> fontMappingsList = new List<FontMapping>();
            
            // Parse each of the font catalogs and build list of font mappings
            foreach (var obj in fontCatalogs)
            {
                TextAsset asset = obj as UnityEngine.TextAsset;
                
                using (XmlReader reader = XmlReader.Create(
                    new global::System.IO.StringReader(asset.text)))
                {
                    bool withinCatalog = false;
                    bool withinFont = false;
                    string fontId = null;
                    
                    while (reader.Read())
                    {
                        // Handle start elements
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            if (reader.Name.Equals("catalog",
                                global::System.StringComparison.OrdinalIgnoreCase))
                            {
                                withinCatalog = true;
                            }
                            else if (reader.Name.Equals("font",
                                global::System.StringComparison.OrdinalIgnoreCase))
                            {
                                if (!withinCatalog)
                                {
                                    throw new global::System.FormatException(
                                        "FontCatalog.xml error, font tag not inside catalog tag.");
                                }
                                
                                withinFont = true;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        if (reader.Name.Equals("id",
                                            global::System.StringComparison.OrdinalIgnoreCase))
                                        {
                                            fontId = reader.Value;
                                        }
                                    }
                                }
                            }
                            else if (reader.Name.Equals("mapping",
                                global::System.StringComparison.OrdinalIgnoreCase))
                            {
                                if (!withinFont)
                                {
                                    throw new global::System.FormatException(
                                        "FontCatalog.xml error, mapping tag not inside font tag.");
                                }
                                
                                string name = null;
                                bool bold = false;
                                bool italic = false;
                                
                                while (reader.HasAttributes && reader.MoveToNextAttribute())
                                {
                                    if (reader.Name.Equals("name",
                                        global::System.StringComparison.OrdinalIgnoreCase))
                                    {
                                        name = reader.Value;
                                    }
                                    else if (reader.Name.Equals("bold",
                                        global::System.StringComparison.OrdinalIgnoreCase))
                                    {
                                        if (reader.Value.Equals("true",
                                            global::System.StringComparison.OrdinalIgnoreCase))
                                        {
                                            bold = true;
                                        }
                                    }
                                    else if (reader.Name.Equals("italic"))
                                    {
                                        if (reader.Value.Equals("true",
                                            global::System.StringComparison.OrdinalIgnoreCase))
                                        {
                                            italic = true;
                                        }
                                    }
                                }
                                
                                if (name != null)
                                {
                                    var mapping = new FontMapping(fontId, name, bold, italic);
                                    fontMappingsList.Add(mapping);
                                }
                            }
                        }
                        // Handle end elements
                        else if (reader.NodeType == XmlNodeType.EndElement)
                        {
                            if (reader.Name.Equals("catalog",
                                global::System.StringComparison.OrdinalIgnoreCase))
                            {
                                withinCatalog = false;
                            }
                            else if (reader.Name.Equals("font",
                                global::System.StringComparison.OrdinalIgnoreCase))
                            {
                                withinFont = true;
                                fontId = null;
                            }
                        }
                    }
                }
            }
            
            // Build lookup table of all the Unity fonts within Resources/Fonts folders
            Dictionary<string, UnityEngine.Font> unityFonts =
                new Dictionary<string, UnityEngine.Font>();
            foreach (var obj in Resources.LoadAll("Fonts", typeof(UnityEngine.Font)))
            {
                UnityEngine.Font font = obj as UnityEngine.Font;
                unityFonts[font.name] = font;
            }

            // Create a device font for each of the mappings
            for (int i = 0; i < fontMappingsList.Count; )
            {
                var mapping = fontMappingsList[i];
                
                UnityEngine.Font unityFont;
                if (unityFonts.TryGetValue(mapping.id, out unityFont))
                {
                    Flare.Text.DeviceFont df = new Flare.Text.DeviceFont(mapping.name,
                        mapping.style, unityFont);
                    mapping.deviceFont = df;
                    ++i;
                }
                else
                {
                    Log.Error(Subsystem.Playback,
                        "Font '{0}' does not exist in a Resources/Fonts folder but " +
                        "is referenced in a FontCatalog.xml file. Discarding '{1}' mapping!",
                        mapping.id, mapping.name);
                    fontMappingsList.RemoveAt(i);
                }
            }

            ms_fontMappings = new Dictionary<string, FontMapping>();
            for (int i = 0; i < fontMappingsList.Count; ++i)
            {
                var mapping = fontMappingsList[i];
                string key = CombineNameAndStyle(mapping.name, mapping.style);
                ms_fontMappings[key] = mapping;
            }

            ms_fontCatalogsLoaded = true;
        }
    
        //-----------------------------------------------------------------------------------------

        private class FontMapping
        {
            /// <summary>
            /// Name of the Unity font object (usually, the base name of the font file)
            /// </summary>
            public string id { get; private set; }

            /// <summary>
            /// Reference to the device font object
            /// </summary>
            public DeviceFont deviceFont { get; internal set; }

            /// <summary>
            /// Name of the font used within SWF files
            /// </summary>
            public string name { get; private set; }

            /// <summary>
            /// Style of the font used within SWF files
            /// </summary>
            public string style { get; private set; }
            
            public FontMapping(string id, string name, bool bold, bool italic)
            {
                this.id = id;
                this.name = name;
                this.style = FontStyle.GetStyle(bold, italic);
            }
            
            public override string ToString ()
            {
                return string.Format("[id={0} name={1} style={2} deviceFont={3}]", 
                    this.id, this.name, this.style, this.deviceFont);
            }
        }
        
        //-----------------------------------------------------------------------------------------
        
        internal class FontMetrics
        {
            public float ascent { get; private set; }
            public float descent { get; private set; }
            public float leading { get; private set; }
            
            public FontMetrics(float ascent, float descent, float leading)
            {
                this.ascent = ascent;
                this.descent = descent;
                this.leading = leading;
            }
        }
    }
}
